package net.phoenix.repository.nodetype;

import java.util.Arrays;
import java.util.Comparator;
import java.util.HashSet;
import java.util.LinkedHashMap;
import java.util.LinkedHashSet;
import java.util.Map;
import java.util.Set;
import java.util.Vector;

import javax.jcr.RepositoryException;
import javax.jcr.Value;
import javax.jcr.nodetype.NoSuchNodeTypeException;
import javax.jcr.nodetype.NodeDefinition;
import javax.jcr.nodetype.NodeType;
import javax.jcr.nodetype.NodeTypeIterator;
import javax.jcr.nodetype.PropertyDefinition;

import net.phoenix.repository.JCRUtils;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import net.phoenix.repository.utils.AntPathMatcher;

/**
 * Jigsaw server
 * 
 * @author <a href="shamphone@gmail.com">Li XiongFeng</a>
 * @date 2012-1-4
 * @version 1.0.0
 */
public abstract class GeneralNodeType implements NodeType {
	protected GeneralNodeTypeManager manager;
	protected static Comparator<PropertyDefinition> comparator;
	/**
	 * 所有简单属性定义，使用缓加载策略，使用时调用properties()方法，而不是直接使用这个变量
	 */
	private LinkedHashMap<String, PropertyDefinition> _allProperties;
	private LinkedHashMap<String, NodeDefinition> _allChildren;
	private static final Log log = LogFactory.getLog(GeneralNodeType.class);

	public GeneralNodeType(GeneralNodeTypeManager manager) {
		this.manager = manager;
		comparator = new PropertyDefinitionComparator();
		this._allProperties = null;
		this._allChildren = null;
	}

	/**
	 * 缓加载所有简单属性
	 * 
	 * @return Map
	 */
	private synchronized Map<String, PropertyDefinition> properties() {
		if (this._allProperties != null) {
			return this._allProperties;
		}
		this._allProperties = new LinkedHashMap<String, PropertyDefinition>();
		this._allProperties.putAll(this.loadInheritedPropertyDefinitions());
		this._allProperties.putAll(this.loadDeclaredPropertyDefinitions());
		log.trace(this._allProperties.size() + " properties loaded for scheme " + this.getName());
		return this._allProperties;
	}

	/**
	 * 缓加载所有简单属性
	 * 
	 * @return Map
	 */
	private synchronized Map<String, NodeDefinition> children() {
		if (this._allChildren != null) {
			return this._allChildren;
		}
		this._allChildren = new LinkedHashMap<String, NodeDefinition>();
		this._allChildren.putAll(this.loadInheritedNodeDefinitions());
		this._allChildren.putAll(this.loadDeclaredNodeDefinitions());
		log.trace(this._allChildren.size() + " children loaded for scheme " + this.getName());
		return this._allChildren;
	}

	/**
	 * 加载本大纲定义的所有属性定义
	 * 
	 * @return
	 */
	protected abstract Map<String, PropertyDefinition> loadDeclaredPropertyDefinitions();

	/**
	 * 加载本大纲定义的所有属性定义
	 * 
	 * @return
	 */
	protected abstract Map<String, NodeDefinition> loadDeclaredNodeDefinitions();

	/**
	 * 加载从父节点继承过来的属性；
	 * 
	 * @return
	 */
	private Map<String, PropertyDefinition> loadInheritedPropertyDefinitions() {
		Map<String, PropertyDefinition> properties = new LinkedHashMap<String, PropertyDefinition>();
		for (NodeType parent : this.getDeclaredSupertypes()) {
			for (PropertyDefinition property : parent.getPropertyDefinitions()) {
				properties.put(property.getName(), property);
			}
		}
		return properties;
	}

	/**
	 * 加载从父节点继承过来的子节点；
	 * 
	 * @return
	 */
	private Map<String, NodeDefinition> loadInheritedNodeDefinitions() {
		Map<String, NodeDefinition> children = new LinkedHashMap<String, NodeDefinition>();
		for (NodeType parent : this.getDeclaredSupertypes()) {
			for (NodeDefinition property : parent.getChildNodeDefinitions()) {
				children.put(property.getName(), property);
			}
		}
		return children;
	}

	/**
	 * @param name
	 * @return
	 */
	public PropertyDefinition getPropertyDefinition(String name) {
		return this.properties().get(name);
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see javax.jcr.nodetype.NodeType#getPropertyDefinitions()
	 */
	@Override
	public PropertyDefinition[] getPropertyDefinitions() {
		return this.properties().values().toArray(new PropertyDefinition[this.properties().size()]);
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see javax.jcr.nodetype.NodeType#getDeclaredSubtypes()
	 */
	@Override
	public NodeTypeIterator getDeclaredSubtypes() {
		Vector<NodeType> v = new Vector<NodeType>();
		for (NodeTypeIterator definitions = this.manager.getAllNodeTypes(); definitions.hasNext();) {
			NodeType definition = definitions.nextNodeType();
			for (String parent : definition.getDeclaredSupertypeNames())
				if (parent.equals(this.getName()))
					v.add(definition);
		}
		return new BasicNodeTypeIterator(v);
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see javax.jcr.nodetype.NodeType#isNodeType(java.lang.String)
	 */
	@Override
	public boolean isNodeType(String superType) {
		if (superType.equals(this.getName()))
			return true;
		for (NodeType parent : this.getDeclaredSupertypes())
			if (parent.isNodeType(superType))
				return true;
		return false;

	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see java.lang.Object#equals(java.lang.Object)
	 */
	@Override
	public boolean equals(Object obj) {
		if (obj == null)
			return false;
		if (!(obj instanceof NodeType))
			return false;
		NodeType another = (NodeType) obj;
		return this.getName().equals(another.getName());
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see java.lang.Object#hashCode()
	 */
	@Override
	public int hashCode() {
		return (this.getClass().getName() + "." + this.getName()).hashCode();
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see javax.jcr.nodetype.NodeType#getSupertypes()
	 */
	@Override
	public NodeType[] getSupertypes() {
		Set<NodeType> v = new LinkedHashSet<NodeType>();
		for (NodeType type : this.getDeclaredSupertypes()) {
			v.add(type);
			v.addAll(Arrays.asList(type.getSupertypes()));
		}
		return v.toArray(new NodeType[v.size()]);
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see javax.jcr.nodetype.NodeType#getDeclaredSupertypes()
	 */
	@Override
	public NodeType[] getDeclaredSupertypes() {
		Set<NodeType> v = new HashSet<NodeType>();
		for (String name : this.getDeclaredSupertypeNames()) {
			try {
				NodeType type = this.manager.getNodeType(name);
				v.add(type);
			} catch (NoSuchNodeTypeException e) {
				throw new IllegalStateException("node type with name '" + name + "' could not be found. ");
			}

		}
		return v.toArray(new NodeType[v.size()]);
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see javax.jcr.nodetype.NodeType#getSubtypes()
	 */
	@Override
	public NodeTypeIterator getSubtypes() {
		Vector<NodeType> v = new Vector<NodeType>();
		for (NodeTypeIterator definitions = this.manager.getAllNodeTypes(); definitions.hasNext();) {
			NodeType definition = definitions.nextNodeType();
			if (definition.isNodeType(this.getName()) && !definition.equals(this))
				v.add(definition);
		}
		return new BasicNodeTypeIterator(v);
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see javax.jcr.nodetype.NodeType#canAddChildNode(java.lang.String)
	 */
	@Override
	public boolean canAddChildNode(String childNodeName) {
		AntPathMatcher matcher = new AntPathMatcher();
		for (NodeDefinition child : this.getChildNodeDefinitions()) {
			if (child.getName().equals(childNodeName))
				return true;
		}
		for (NodeDefinition child : this.getChildNodeDefinitions()) {
			if (matcher.match(child.getName(), childNodeName))
				return true;
		}

		return false;
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see javax.jcr.nodetype.NodeType#canAddChildNode(java.lang.String,
	 * java.lang.String)
	 */
	@Override
	public boolean canAddChildNode(String childNodeName, String nodeTypeName) {
		AntPathMatcher matcher = new AntPathMatcher();
		NodeType nodeType = null;
		try {
			nodeType = this.manager.getNodeType(nodeTypeName);
		} catch (NoSuchNodeTypeException e) {
			throw new IllegalArgumentException("node type with name '" + nodeTypeName + "' could not be found. ");
		}
		for (NodeDefinition child : this.getChildNodeDefinitions()) {
			if (child.getName().equals(childNodeName) && nodeType.isNodeType(child.getDefaultPrimaryTypeName()))
				return true;
		}
		for (NodeDefinition child : this.getChildNodeDefinitions()) {
			if (matcher.match(child.getName(), childNodeName) && nodeType.isNodeType(child.getDefaultPrimaryTypeName()))
				return true;
		}

		return false;
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see javax.jcr.nodetype.NodeType#canRemoveItem(java.lang.String)
	 */
	@Override
	public boolean canRemoveItem(String itemName) {

		return true;
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see javax.jcr.nodetype.NodeType#canRemoveNode(java.lang.String)
	 */
	@Override
	public boolean canRemoveNode(String nodeName) {
		return true;
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see javax.jcr.nodetype.NodeType#canRemoveProperty(java.lang.String)
	 */
	@Override
	public boolean canRemoveProperty(String propertyName) {
		return true;
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see javax.jcr.nodetype.NodeType#canSetProperty(java.lang.String,
	 * javax.jcr.Value)
	 */
	@Override
	public boolean canSetProperty(String property, Value value) {
		PropertyDefinition definition = this.getPropertyDefinition(property);
		if (definition == null)
			throw new IllegalArgumentException("Unknown property name:" + property + ".");
		try {
			return JCRUtils.checkType(value, definition.getRequiredType());
		} catch (RepositoryException e) {
			return false;
		}

	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see javax.jcr.nodetype.NodeType#canSetProperty(java.lang.String,
	 * javax.jcr.Value[])
	 */
	@Override
	public boolean canSetProperty(String property, Value[] values) {
		PropertyDefinition definition = this.getPropertyDefinition(property);
		if (definition == null)
			throw new IllegalArgumentException("Unknown property name:" + property + ".");
		try {
			if (values != null)
				for (int i = 0; i < values.length; i++)
					if (!JCRUtils.checkType(values[i], definition.getRequiredType()))
						return false;
			return true;
		} catch (RepositoryException e) {
			return false;
		}
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see javax.jcr.nodetype.NodeType#getChildNodeDefinitions()
	 */
	@Override
	public NodeDefinition[] getChildNodeDefinitions() {
		return this.children().values().toArray(new NodeDefinition[this.children().size()]);
	}

}
